package com.wb.spring.boot.autoconfigure.proxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.RestTemplate;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;


public class WbClientCall {
    static Logger logger = LoggerFactory.getLogger(WbClientCall.class);

    // 全局默认最多重试次数
    private int globalMaxAttempts = 0;

    // 全局异步请求开关
    private boolean globalAsync = false;

    // 集成httpclient4, httpclient5, okhttp3
    @Autowired
//    @Autowired(required = false)
    private RestTemplate restTemplate;

    @Autowired
//    @Autowired(required = false)
    private AsyncRestTemplate asyncRestTemplate;

    public WbClientCall() {

    }

    public WbClientCall(boolean globalAsync) {
        this.globalAsync = globalAsync;
    }

    public WbClientCall(int globalMaxAttempts) {
        this.globalMaxAttempts = globalMaxAttempts;
    }

    public WbClientCall(boolean globalAsync, int globalMaxAttempts) {
        this.globalAsync = globalAsync;
        this.globalMaxAttempts = globalMaxAttempts;
    }


    /**
     * 实际请求
     *
     * @param uri          请求路径
     * @param method       请求方法
     * @param requestBody  body参数
     * @param uriVariables uri參數
     * @param clazz        返回结果类
     * @param <T>          泛型
     * @return 返回值
     */
    public <T> T call(String uri, String method, Object requestBody, Map<String, Object> uriVariables, Class<T> clazz, boolean async, int maxAttempts) throws Throwable {
        logger.info("call params { uri = {}, method = {}, requestBody = {}, uriVariables = {}, clazz = {}, async = {}, maxAttempts = {} }", uri, method, requestBody, uriVariables, clazz, async, maxAttempts);
        maxAttempts = (maxAttempts > 0) ? maxAttempts : (Math.max(globalMaxAttempts, 0));
        if (maxAttempts > 0) {
            return maxAttemptCall(uri, method, requestBody, uriVariables, clazz, async, maxAttempts);
        } else {
            return basicCall(uri, method, requestBody, uriVariables, clazz, async);
        }
    }

    public <T> T maxAttemptCall(String uri, String method, Object requestBody, Map<String, Object> uriVariables, Class<T> clazz, boolean async, int maxAttempts) throws Throwable {
        if (globalAsync || async) {
            return maxAttemptAsyncCall(uri, method, requestBody, uriVariables, clazz, maxAttempts);
        }
        return maxAttemptSyncCall(uri, method, requestBody, uriVariables, clazz, maxAttempts);
    }

    public <T> T maxAttemptAsyncCall(String uri, String method, Object requestBody, Map<String, Object> uriVariables, Class<T> clazz, int maxAttempts) throws Throwable {
        int step = 0;
        Throwable throwable = null;

        // TODO
        for (step = 0; step < maxAttempts; step++) {
            try {
                return asyncCall(uri, method, requestBody, uriVariables, clazz);
            } catch (Exception e) {
                throwable = e;
            }
        }
        if (step >= maxAttempts) {
            throw throwable;
        }
        return null;
    }

    public <T> T maxAttemptSyncCall(String uri, String method, Object requestBody, Map<String, Object> uriVariables, Class<T> clazz, int maxAttempts) throws Throwable {
        int step = 0;
        Throwable throwable = null;
        //
        for (step = 0; step < maxAttempts; step++) {
            try {
                return syncCall(uri, method, requestBody, uriVariables, clazz);
            } catch (Exception e) {
                throwable = e;
            }
        }
        if (step >= maxAttempts) {
            throw throwable;
        }
        return null;
    }


    public <T> T basicCall(String uri, String method, Object requestBody, Map<String, Object> uriVariables, Class<T> clazz, boolean async) throws ExecutionException, InterruptedException {
        if (globalAsync || async) {
            return asyncCall(uri, method, requestBody, uriVariables, clazz);
        }
        return syncCall(uri, method, requestBody, uriVariables, clazz);
    }

    /**
     * 同步调用
     *
     * @param uri
     * @param method
     * @param requestBody
     * @param uriVariables
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T syncCall(String uri, String method, Object requestBody, Map<String, Object> uriVariables, Class<T> clazz) {

        HttpEntity<?> requestEntity = new HttpEntity<>(requestBody);

        ResponseEntity<T> entityBody = null;
        switch (method) {
            case "GET":
                entityBody = restTemplate.getForEntity(uri, clazz);
                break;
            case "POST":
//                entityBody = restTemplate.postForEntity(uri, requestBody, clazz);
                entityBody = restTemplate.postForEntity(uri, requestEntity, clazz);
                break;
            case "PUT":
                //
                entityBody = restTemplate.exchange(
                        uri,
                        HttpMethod.PUT, requestEntity, clazz, uriVariables);
                break;
            case "DELETE":
                entityBody = restTemplate.exchange(
                        uri,
                        HttpMethod.DELETE, requestEntity, clazz, uriVariables);
                break;

        }

        if (clazz == Void.class) {
            return null;
        }
        return Optional.ofNullable(entityBody).isPresent() ? entityBody.getBody() : null;
    }

    /**
     * 异步调用
     *
     * @param uri
     * @param method
     * @param requestBody
     * @param uriVariables
     * @param clazz
     * @param <T>
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public <T> T asyncCall(String uri, String method, Object requestBody, Map<String, Object> uriVariables, Class<T> clazz) throws ExecutionException, InterruptedException {

        HttpEntity<?> requestEntity = new HttpEntity<>(requestBody);

        ResponseEntity<T> entityBody = null;
        switch (method) {
            case "GET":
                entityBody = asyncRestTemplate.getForEntity(uri, clazz).get();
                break;
            case "POST":
                entityBody = asyncRestTemplate.postForEntity(uri, requestEntity, clazz).get();
//                entityBody = asyncRestTemplate.postForEntity(uri, requestBody, clazz);
                break;
            case "PUT":
                //
                entityBody = asyncRestTemplate.exchange(
                        uri,
                        HttpMethod.PUT, requestEntity, clazz, uriVariables).get();
                break;
            case "DELETE":
                entityBody = asyncRestTemplate.exchange(
                        uri,
                        HttpMethod.DELETE, requestEntity, clazz, uriVariables).get();
                break;

        }

        if (clazz == Void.class) {
            return null;
        }
        return Optional.ofNullable(entityBody).isPresent() ? entityBody.getBody() : null;
    }
}
